package com.hmall.item.util;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmall.common.domain.PageDTO;
import com.hmall.item.domain.dto.ItemDoc;
import com.hmall.item.domain.po.Item;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.Stats;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Slf4j
@Component
@RequiredArgsConstructor
public class ItemSyncEsUtil {

    private RestHighLevelClient client;

    @PostConstruct
    public void init() {
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.150.101:9200")
        ));
    }

    @PreDestroy
    public void destroy() throws IOException {
        if (client != null) {
            client.close();
        }
    }

    /**
     * 同步单个商品到ES
     *
     * @param item 商品信息
     */
    public void syncItemToEs(Item item) {
        try {
            ItemDoc itemDoc = new ItemDoc(item);
            IndexRequest request = new IndexRequest("items").id(itemDoc.getId().toString())
                    .source(JSONUtil.toJsonStr(itemDoc), XContentType.JSON);
            client.index(request, RequestOptions.DEFAULT);
            log.info("商品同步到ES成功，商品ID: {}", item.getId());
        } catch (IOException e) {
            log.error("商品同步到ES失败，商品ID: {}", item.getId(), e);
        }
    }

    /**
     * 更新ES中的商品信息
     *
     * @param item 商品信息
     */
    public void updateItemToEs(Item item) {
        try {
            ItemDoc itemDoc = new ItemDoc(item);
            UpdateRequest request = new UpdateRequest("items", itemDoc.getId().toString())
                    .doc(JSONUtil.toJsonStr(itemDoc), XContentType.JSON)
                    .upsert(JSONUtil.toJsonStr(itemDoc), XContentType.JSON);
            client.update(request, RequestOptions.DEFAULT);
            log.info("更新ES中的商品信息成功，商品ID: {}", item.getId());
        } catch (IOException e) {
            log.error("更新ES中的商品信息失败，商品ID: {}", item.getId(), e);
        }
    }

    /**
     * 从ES中删除商品信息
     *
     * @param id 商品ID
     */
    public void deleteItemFromEs(Long id) {
        try {
            DeleteRequest request = new DeleteRequest("items", id.toString());
            client.delete(request, RequestOptions.DEFAULT);
            log.info("从ES中删除商品信息成功，商品ID: {}", id);
        } catch (IOException e) {
            log.error("从ES中删除商品信息失败，商品ID: {}", id, e);
        }
    }
    
    /**
     * 新增商品到ES
     * 对应Controller中的saveItem方法
     *
     * @param item 新增的商品信息
     */
    public void saveItemToEs(Item item) {
        syncItemToEs(item);
    }


    /**
     * 发起请求并解析响应结果
     *
     * @param request
     * @throws IOException
     */
    public PageDTO requestAndParseResponseResult(SearchRequest request, Class dtoClass) throws Exception {
        // 3.发送请求
        SearchResponse response = getResponse(request);
        // 4.处理响应
        SearchHits hits = response.getHits();
        // 5.1、获取总条数
        long total = hits.getTotalHits().value;
        // 5.2、获取文档数组
        SearchHit[] searchHits = hits.getHits();
        if (ObjUtil.isEmpty(searchHits)) {
            return new PageDTO<>();
        }
        List<Object> objects = new ArrayList<>(searchHits.length);
        for (SearchHit hit : hits) {
            // 1.获取文档的json字符串
            String sourceAsString = hit.getSourceAsString();
            // 2.将json字符串转换为ItemDoc
            Object object = JSONUtil.toBean(sourceAsString, dtoClass);
            // 3.将itemDoc添加到集合中
            objects.add(object);
            // 4.获取高亮结果
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (ObjUtil.isNotEmpty(highlightFields)) {
                for (HighlightField highlightField : highlightFields.values()) {
                    // 获取高亮字段名
                    String name = highlightField.getName();
                    // 获取高亮字段值
                    String fieldValue = highlightField.getFragments()[0].toString();
                    // 反射设置属性值（方式一：通过私有字段进行暴力赋值）
                    Field declaredField = dtoClass.getDeclaredField(name);
                    if (ObjUtil.isNotEmpty(declaredField)) {
                        // 开启暴力反射
                        declaredField.setAccessible(true);
                        // 为字段赋值
                        declaredField.set(object, fieldValue);
                    }
                    // 方式二：通过getter和setter进行赋值
//                    Method method = dtoClass.getMethod("set" + StrUtil.upperFirst(name), String.class);
//                    method.invoke(object, fieldValue);
                }
            }
        }
//        objects.forEach(System.out::println);
//        System.out.println(total);
        return new PageDTO<>(total, 1L, objects);
    }

    public SearchResponse getResponse(SearchRequest request) throws IOException {
        return client.search(request, RequestOptions.DEFAULT);
    }

    /**
     * 桶聚合结果解析
     */
    public List<String> parseAggResults(SearchRequest request, String buckAggName, String statsAggName) throws Exception {
        // 3.发送请求
        SearchResponse response = getResponse(request);
        // 4.处理响应
        // 4.1获取桶聚合结果
        Aggregations aggregations = response.getAggregations();
        // 添加空指针检查
        if (aggregations == null) {
            return new ArrayList<>();
        }
        // 4.2根据聚合名字获取桶聚合对象
        Terms brandAgg = aggregations.get(buckAggName);
        // 添加空指针检查
        if (brandAgg == null) {
            return new ArrayList<>();
        }
        // 4.3获取桶聚合对象中的桶（数据集合）
        List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
        // 添加空指针检查
        if (buckets == null) {
            return new ArrayList<>();
        }
        // 4.4循环桶中的数据，放入集合中
        List<String> brands = new ArrayList<>(buckets.size());
        for (Terms.Bucket bucket : buckets) {
            String aggResult = bucket.getKeyAsString();
            long docCount = bucket.getDocCount();
            brands.add(aggResult);
            // 4.5解析度量聚合结果
            if (StrUtil.isNotBlank(statsAggName)) {
                Aggregations bucketAggregations = bucket.getAggregations();
                if (bucketAggregations != null) {
                    Stats priceAgg = bucketAggregations.get(statsAggName);
                    if (priceAgg != null) {
                        double avg = priceAgg.getAvg();
                        System.out.println("\tavg" + avg);
                        double max = priceAgg.getMax();
                        System.out.println("\tmax" + max);
                        double min = priceAgg.getMin();
                        System.out.println("\tmin" + min);
                        double sum = priceAgg.getSum();
                        System.out.println("\tsum" + sum);
                    }
                }
            }
            System.out.println(aggResult + " = " + docCount);
        }
        return brands;
    }
}
